home *** CD-ROM | disk | FTP | other *** search
/ BBS in a Box 4 / BBS in a Box - Macintosh - Volume IV (January 1992) (BBS in a Box).iso / Files / Prog / B-C / ColorCollect.p < prev    next >
Encoding:
Text File  |  1991-02-23  |  14.0 KB  |  582 lines  |  [TEXT/MACA]

  1. Program ColorCollect;
  2.  
  3. (*
  4.  
  5. purpose    To demonstrate figuring out the colors used in a picture.
  6.                 It figures out which colors a picture uses,
  7.                 attaches a palette to a color window, and displays the picture.
  8.  
  9.                 This program was written by Darin Adler, 2/88
  10.                 based on SillyBalls by Bo3b Johnson.
  11.  
  12.                 You can build the program with this:
  13.  
  14. Pascal ColorCollect.p
  15. Link ColorCollect.p.o ∂
  16.     "{Libraries}Interface.o" ∂
  17.     "{Libraries}Runtime.o" ∂
  18.     "{PLibraries}PasLib.o" ∂
  19.     -o ColorCollect
  20. ColorCollect
  21.  
  22. *)
  23.  
  24. USES
  25.     MemTypes, QuickDraw, OSIntf, ToolIntf, PackIntf, PaletteMgr;
  26.  
  27. TYPE
  28.     BitMapPtr = ^BitMap;
  29.  
  30. VAR
  31.     gWindow:    WindowPtr;
  32.  
  33.  
  34. { Figure out whether a BitMap is really a PixMap, and return a PixMapPtr. }
  35.  
  36. Function GetPixMapPtr(bitPtr: BitMapPtr): PixMapPtr;
  37.  
  38. { If this BitMap is a real BitMap, return NIL.  There are three cases here:
  39.     1) real BitMap; high bit (PixMapBit) of rowBytes not set
  40.     2) pointer to PixMap; high bit (PixMapBit) of rowBytes set
  41.     3) pointer to PixMapHandle in a port; both high bits of rowBytes set}
  42.  
  43.     CONST
  44.         PixMapBit = 15;
  45.         PortPixMapBit = 14;
  46.  
  47.     TYPE
  48.         PixMapHandlePtr = ^PixMapHandle;    {used for tricky stuff with PixMaps}
  49.  
  50.     BEGIN
  51.     WITH bitPtr^ DO
  52.         IF BTst(rowBytes, PixMapBit) THEN
  53.             IF BTst(rowBytes, PortPixMapBit) THEN
  54.                 { If both high bits are set, we have a pointer to a PixMapHandle. }
  55.                 GetPixMapPtr := PixMapHandlePtr(bitPtr)^^
  56.             ELSE
  57.                 { If one high bit of rowBytes is set, we have a pointer to a
  58.                     PixMap, which is just what we want. }
  59.                 GetPixMapPtr := PixMapPtr(bitPtr)
  60.         ELSE
  61.             { If the high bit of rowBytes is not set, we don't have a PixMap. }
  62.             GetPixMapPtr := NIL;
  63.     END;
  64.  
  65.  
  66. { Bottlenecks for CollectColors routine below. }
  67.  
  68. VAR
  69.     gColorError:        OSErr;            { Used to report errors from bottlenecks. }
  70.     gColorTable:        CTabHandle;    { Used to collect colors from bottlenecks. }
  71.  
  72.  
  73. { Given a value for ctSize, calculate the size that the color table should be. }
  74.  
  75. Function SizeOfColorTable(ctSize: Integer): LongInt;
  76.  
  77. BEGIN
  78.     SizeOfColorTable := SizeOf(ColorTable) + SizeOf(ColorSpec) *
  79.         LongInt(ctSize);
  80. END;
  81.  
  82.  
  83. { Check if the two colors are identical. }
  84.  
  85. Function EqualColor(color1, color2: RGBColor): Boolean;
  86.  
  87. BEGIN
  88.     EqualColor := (color1.red = color2.red) AND (color1.green = color2.green)
  89.         AND (color1.blue = color2.blue);
  90. END;
  91.  
  92.  
  93. { Check if a color is already in the color table. }
  94.  
  95. Function ColorInTable(color: RGBColor): Boolean;
  96.  
  97. VAR
  98.     index: Integer;
  99.  
  100. BEGIN
  101.     WITH gColorTable^^ DO
  102.         FOR index := 0 TO ctSize DO
  103.             { We have to turn off range checking here so that we can index into
  104.                 ctTable.  It is declared as an ARRAY[0..0], but it has more than one
  105.                 element. }
  106.             {$PUSH}{$R-}
  107.             IF EqualColor(color, ctTable[index].rgb) THEN BEGIN
  108.             { This sets the options (range checking) back. }
  109.             {$POP}
  110.                 ColorInTable := TRUE;
  111.                 EXIT(ColorInTable);
  112.             END;
  113.     ColorInTable := FALSE;
  114. END;
  115.  
  116.  
  117. { Add a color to the color table. }
  118.  
  119. Procedure AddRGBColor(color: RGBColor);
  120.  
  121. BEGIN
  122.     { Don't add any more colors if there has already been a color error. }
  123.     IF gColorError = noErr THEN
  124.         { Don't add a color if it is already in the table. }
  125.         IF NOT ColorInTable(color) THEN BEGIN
  126.             WITH gColorTable^^ DO BEGIN
  127.                 { Add an entry to the color table. }
  128.                 ctSize := ctSize + 1;
  129.                 SetHandleSize(Handle(gColorTable), SizeOfColorTable(ctSize));
  130.                 gColorError := MemError;
  131.             END;
  132.             IF gColorError = noErr THEN
  133.                 WITH gColorTable^^ DO
  134.                     { We have to turn off range checking here so that we can index
  135.                         into ctTable.  It is declared as an ARRAY[0..0], but it has more
  136.                         than one element. }
  137.                     {$PUSH}{$R-}
  138.                     ctTable[ctSize].rgb := color;
  139.                     { This sets the options (range checking) back. }
  140.                     {$POP}
  141.         END;
  142. END;
  143.  
  144.  
  145. { Add the contents of another color table to our color table. }
  146.  
  147. Procedure AddColorTable(cTab: CTabHandle);
  148.  
  149. VAR
  150.     index:    Integer;
  151.  
  152. BEGIN
  153.     FOR index := 0 TO cTab^^.ctSize DO
  154.         { We have to turn off range checking here so that we can index into
  155.             ctTable.  It is declared as an ARRAY[0..0], but it has more than one
  156.             element. }
  157.         {$PUSH}{$R-}
  158.         AddRGBColor(cTab^^.ctTable[index].rgb);
  159.         { This sets the options (range checking) back. }
  160.         {$POP}
  161. END;
  162.  
  163.  
  164. { Add the foreground color of the current port to the color table. }
  165.  
  166. Procedure AddRGBForeColor;
  167.  
  168. BEGIN
  169.     AddRGBColor(CGrafPtr(thePort)^.rgbFgColor);
  170. END;
  171.  
  172.  
  173. { Add the background color of the current port to the color table. }
  174.  
  175. Procedure AddRGBBackColor;
  176.  
  177. BEGIN
  178.     AddRGBColor(CGrafPtr(thePort)^.rgbBkColor);
  179. END;
  180.  
  181.  
  182. { Add colors from a PixPat to a color table. }
  183.  
  184. Procedure AddPixPat(pPat: PixPatHandle);
  185.  
  186. BEGIN
  187.     CASE pPat^^.patType OF
  188.         0: BEGIN
  189.             { Type 0 PixPats are one-bit patterns; they are drawn in the
  190.                 foreground and background color. }
  191.             AddRGBForeColor;
  192.             AddRGBBackColor;
  193.         END;
  194.         1:
  195.             { Type 1 PixPats have a color table. }
  196.             AddColorTable(pPat^^.patMap^^.pmTable);
  197.     END;
  198. END;
  199.  
  200.  
  201. { Add colors from the pen PixPat to the color table. }
  202.  
  203. Procedure AddPenPixPat;
  204.  
  205. BEGIN
  206.     AddPixPat(CGrafPtr(thePort)^.pnPixPat);
  207. END;
  208.  
  209.  
  210. { Add colors from the fill PixPat to the color table. }
  211.  
  212. Procedure AddFillPixPat;
  213.  
  214. BEGIN
  215.     AddPixPat(CGrafPtr(thePort)^.fillPixPat);
  216. END;
  217.  
  218.  
  219. { Add colors because we are about to draw some text. }
  220.  
  221. Procedure ColorTextProc(byteCount: Integer; textBuf: Ptr; numer, denom:
  222.     Point);
  223.  
  224. BEGIN
  225.     { Text is drawn with the foreground and background colors. }
  226.     AddRGBForeColor;
  227.     AddRGBBackColor;
  228.         DebugStr('Text')
  229.  
  230. END;
  231.  
  232.  
  233. { Add colors because we are about to draw a line. }
  234.  
  235. Procedure ColorLineProc(newPt: Point);
  236.  
  237. BEGIN
  238.     { Lines are drawn with the pen PixPat. }
  239.     AddPenPixPat;
  240.         DebugStr('Line')
  241.  
  242. END;
  243.  
  244.  
  245. { Add colors because we are about to draw an object. }
  246.  
  247. Procedure AddVerb(verb: GrafVerb);
  248.  
  249. BEGIN
  250.     CASE verb OF
  251.         frame, paint:
  252.             { Framed objects and painted objects are drawn in the pen PixPat. }
  253.             AddPenPixPat;
  254.         erase:
  255.             { Erased objects are drawn in the background color. }
  256.             AddRGBBackColor;
  257.         fill:
  258.             { Filled objects are drawn in the fill PixPat.  The fillPixPat is
  259.                 a pattern used to record fill commands for pictures.  First, a
  260.                 command to set the fillPixPat is recorded, then the fill command
  261.                 is recorded. }
  262.             AddFillPixPat;
  263.     END;
  264. END;
  265.  
  266.  
  267. { Add colors because we are about to draw a rectangle. }
  268.  
  269. Procedure ColorRectProc(verb: GrafVerb; r: Rect);
  270.  
  271. BEGIN
  272.     { Each verb is different (Frame, Paint, Erase, Fill). }
  273.     AddVerb(verb);
  274.         DebugStr('Rect')
  275.  
  276. END;
  277.  
  278.  
  279. { Add colors because we are about to draw a rounded rectangle. }
  280.  
  281. Procedure ColorRRectProc(verb: GrafVerb; r: Rect; ovalWidth, ovalHeight:
  282.     Integer);
  283.  
  284. BEGIN
  285.     { Each verb is different (Frame, Paint, Erase, Fill). }
  286.     AddVerb(verb);
  287.         DebugStr('RRect')
  288.  
  289. END;
  290.  
  291.  
  292. { Add colors because we are about to draw an oval. }
  293.  
  294. Procedure ColorOvalProc(verb: GrafVerb; r: Rect);
  295.  
  296. BEGIN
  297.     { Each verb is different (Frame, Paint, Erase, Fill). }
  298.     AddVerb(verb);
  299.         DebugStr('Oval')
  300.  
  301. END;
  302.  
  303.  
  304. { Add colors because we are about to draw an arc. }
  305.  
  306. Procedure ColorArcProc(verb: GrafVerb; r: Rect; startAngle, arcAngle:
  307.     Integer);
  308.  
  309. BEGIN
  310.     { Each verb is different (Frame, Paint, Erase, Fill). }
  311.     AddVerb(verb);
  312.         DebugStr('Arc')
  313.  
  314. END;
  315.  
  316.  
  317. { Add colors because we are about to draw a polygon. }
  318.  
  319. Procedure ColorPolyProc(verb: GrafVerb; poly: PolyHandle);
  320.  
  321. BEGIN
  322.     { Each verb is different (Frame, Paint, Erase, Fill). }
  323.     AddVerb(verb);
  324.         DebugStr('Poly');
  325.         StdPoly(verb, poly);
  326.         
  327. END;
  328.  
  329.  
  330. { Add colors because we are about to draw a region. }
  331.  
  332. Procedure ColorRgnProc(verb: GrafVerb; rgn: RgnHandle);
  333.  
  334. BEGIN
  335.     { Each verb is different (Frame, Paint, Erase, Fill). }
  336.     AddVerb(verb);
  337.         DebugStr('Rgn')
  338.  
  339. END;
  340.  
  341.  
  342. { Add colors because we are about to draw a BitMap or PixMap. }
  343.  
  344. Procedure ColorBitsProc(srcBitsPtr: BitMapPtr; VAR srcRect, dstRect: Rect;
  345.     mode: Integer; maskRgn: RgnHandle);
  346.  
  347. VAR
  348.     aPixMap:    PixMapPtr;
  349.  
  350. BEGIN
  351.     { Get the PixMap that we are about to draw.  SrcBits might be a BitMap, or
  352.         one of two different kinds of PixMap pointers.  See GetPixMapPtr for
  353.         more information. }
  354.     aPixMap := GetPixMapPtr(srcBitsPtr);
  355.     IF aPixMap = NIL THEN BEGIN
  356.         { It's just a BitMap; it will use the background and foreground colors. }
  357.         AddRGBBackColor;
  358.         AddRGBForeColor;
  359.     END ELSE
  360.         { It's a PixMap; it has its own color table. }
  361.         AddColorTable(aPixMap^.pmTable);
  362.         DebugStr('BitMap')
  363.  
  364. END;
  365.  
  366.  
  367. { Beep out, and exit to shell.  A quick way of handling fatal errors, used
  368.     here for simplicity. }
  369.  
  370. Procedure BeepOut;
  371. BEGIN
  372.     SysBeep (50);
  373.     ExitToShell;                    { We must leave. }
  374. END;
  375.  
  376.  
  377. { Initialize everything, make sure we can run. }
  378.  
  379. Procedure Initialize;
  380.  
  381. VAR
  382.     error:                 OSErr;
  383.     theWorld:            SysEnvRec;
  384.     windowRect:    Rect;
  385.  
  386. BEGIN
  387.     { Test the computer to be sure we can do color.  If not we would crash,
  388.         which would be bad.  If we can’t run, just beep and exit. }
  389.     error := SysEnvirons(1, theWorld);
  390.     IF NOT theWorld.hasColorQD THEN
  391.         BeepOut;            { If no color QD, we must leave. }
  392.  
  393.     { Initialize all the needed managers. }
  394.     InitGraf(@thePort);
  395.     InitFonts;
  396.     InitWindows;
  397.     InitMenus;
  398.     TEInit;
  399.     InitDialogs(NIL);
  400.     InitCursor;
  401.  
  402.     { Make a window for drawing in; it must be a color window.  The window
  403.         is full screen size, made smaller to make it more visible. }
  404.     windowRect := screenBits.bounds;
  405.     InsetRect (windowRect, 50, 50);
  406.     gWindow := NewCWindow(NIL, windowRect, 'Carefully Chosen Colors',
  407.         TRUE, documentProc,  Pointer(-1), FALSE, 0);
  408.     SetPort(gWindow);
  409. END;    { Initialize }
  410.  
  411.  
  412. { Read in a PICT file, return NIL if we fail. }
  413.  
  414. Procedure ReadPicture(VAR aPicture: PicHandle);
  415.  
  416. CONST
  417.     headerSize = 512;    { PICT files have 512-byte headers. }
  418.  
  419. VAR
  420.     where:            Point;
  421.     types:            SFTypeList;
  422.     reply:            SFReply;
  423.     error:            OSErr;
  424.     refNum:        Integer;
  425.     fileSize:        LongInt;
  426.     pictureSize:    LongInt;
  427.  
  428.     { Use this to check error codes and exit if we fail. }
  429.  
  430.     Procedure Check(error: OSErr);
  431.     BEGIN
  432.         IF error <> noErr THEN BEGIN
  433.  
  434.             { Get rid of a picture that I might have started reading. }
  435.             IF aPicture <> NIL THEN BEGIN
  436.                 DisposHandle(Handle(aPicture));
  437.                 aPicture := NIL;
  438.             END;
  439.             { Close the file, if I already opened it. }
  440.             IF refNum <> 0 THEN BEGIN
  441.                 error := FSClose(refNum);
  442.                 refNum := 0;
  443.             END;
  444.  
  445.             { Exit out of the whole ReadPicture shebang. }
  446.             EXIT(ReadPicture);
  447.             END;
  448.     END;
  449.  
  450. BEGIN
  451.     { Set up things so that the Check routine knows we didn't do
  452.         anything yet. }
  453.     aPicture := NIL;
  454.     refNum := 0;
  455.  
  456.     { Call the Standard File package to open a PICT file.  }
  457.     SetPt(where, 100, 100);
  458.     types[0] := 'PICT';
  459.     SFGetFile(where, '', NIL, 1, types, NIL, reply);
  460.  
  461.     { If we found a PICT file, open it and read in the picture. }
  462.     IF reply.good THEN BEGIN
  463.         Check(FSOpen(reply.fName, reply.vRefNum, refNum));
  464.         Check(GetEOF(refNum, fileSize));
  465.         { Make a handle to hold the picture.  The picture is everything
  466.             in the file after the header. }
  467.         pictureSize := fileSize - headerSize;
  468.         aPicture := PicHandle(NewHandle(pictureSize));
  469.         Check(MemError);
  470.         { Skip over the header. }
  471.         Check(SetFPos(refNum, fsFromStart,  headerSize));
  472.         { Read in the data. }
  473.         Check(FSRead(refNum, pictureSize, Ptr(aPicture^)));
  474.         Check(FSClose(refNum));
  475.     END;
  476. END;
  477.  
  478.  
  479. { Figure out a color table for a picture. }
  480.  
  481. Procedure CollectColors(fromPicture: PicHandle; VAR colors: CTabHandle);
  482.  
  483. VAR
  484.     bottlenecks:    CQDProcs;
  485.  
  486. BEGIN
  487.     { Create the bottlenecks to figure out the colors.  These bottlenecks
  488.         will figure out what colors are in a picture, but won't draw anything.
  489.         Note that the bottlenecks are installed in thePort, which must be a
  490.         color port. }
  491.     SetStdCProcs(bottlenecks);
  492.     WITH bottlenecks DO BEGIN
  493.         textProc := @ColorTextProc;
  494.         lineProc := @ColorLineProc;
  495.         rectProc := @ColorRectProc;
  496.         rRectProc := @ColorRRectProc;
  497.         ovalProc := @ColorOvalProc;
  498.         arcProc := @ColorArcProc;
  499.         polyProc := @ColorPolyProc;
  500.         rgnProc := @ColorRgnProc;
  501.         bitsProc := @ColorBitsProc;
  502.     END;
  503.  
  504.     { Create a color table containing black and white. }
  505.     colors := CTabHandle(NewHandle(SizeOfColorTable(1)));
  506.     IF colors <> NIL THEN BEGIN
  507.         WITH colors^^ DO BEGIN
  508.             ctSize := 1; {2 entries}
  509.             WITH ctTable[0].rgb DO BEGIN {first entry is white}
  510.                 red := $FFFF;
  511.                 green := $FFFF;
  512.                 blue := $FFFF;
  513.             END;
  514.             { We have to turn off range checking here so that we can index into
  515.                 ctTable.  It is declared as an ARRAY[0..0], but it has more than one
  516.                 element. }
  517.             {$PUSH}{$R-}
  518.             WITH ctTable[1].rgb DO BEGIN {second entry is black}
  519.             { This sets the options (range checking) back. }
  520.             {$POP}
  521.                 red := 0;
  522.                 green := 0;
  523.                 blue := 0;
  524.             END;
  525.         END;
  526.  
  527.         { Now play back the picture to get the colors.  The dstRect doesn't
  528.             matter since our bottlenecks will never actually draw. We use global
  529.             variables (gColorError and gColorTable) to communicate with the
  530.             bottlenecks. }
  531.         thePort^.grafProcs := @bottlenecks;
  532.         gColorError := noErr;
  533.         gColorTable := colors;
  534.         DrawPicture(fromPicture, fromPicture^^.picFrame);
  535.         thePort^.grafProcs := NIL;
  536.  
  537.         { Fail if error occurred while within the color bottlenecks. }
  538.         IF gColorError <> noErr THEN BEGIN
  539.             DisposHandle(Handle(colors));
  540.             colors := NIL;
  541.         END;
  542.     END;
  543. END;
  544.  
  545.  
  546. VAR
  547.     mainPicture:    PicHandle;
  548.     mainColors:        CTabHandle;
  549.     mainPalette:    PaletteHandle;
  550.     mainRect:            Rect;
  551.  
  552. BEGIN    { Main body of program }
  553.     Initialize;
  554.  
  555.     { Read in the picture. }
  556.     ReadPicture(mainPicture);
  557.     IF mainPicture = NIL THEN
  558.         BeepOut;
  559.  
  560.     { Generate a color table for the picture. }
  561.     CollectColors(mainPicture, mainColors);
  562.     IF mainColors = NIL THEN
  563.         BeepOut;
  564.  
  565.     { Attach a palette to the window (create it from the color table). }
  566.     mainPalette := NewPalette(mainColors^^.ctSize + 1, mainColors,
  567.         pmTolerant, 0);
  568.     IF mainPalette = NIL THEN
  569.         BeepOut;
  570.     SetPalette(gWindow, mainPalette, TRUE);
  571.  
  572.     { Offset the picture to the top left of the window, and display it. }
  573.     mainRect := mainPicture^^.picFrame;
  574.     OffsetRect(mainRect, -mainRect.left, -mainRect.top);
  575.     IF Button THEN
  576.       SetRect(mainRect,0,0,8,8);
  577.     DrawPicture(mainPicture, mainRect);
  578.  
  579.     Repeat
  580.     Until Button;
  581. END.    { ColorCollect }